-- http://grammar.ccc.commnet.edu/grammar/definitions.htm

Sentence = class("Sentence")

Sentence.Abreviations =
{
	Sentence = "snt",
	Subject = "s",
	Predicate = "p",
	Conjuction = "c",
	Verb = "v",
	Adverb = "a",
	Adjective = "adj",
	DirectObject = "doj",
	DirectObjectComplement = "doc",
	IndirectObject = "ioj",
	Negation = "not"
}

Sentence.ModifiersAbreviation = {}
Sentence.ModifiersAbreviation[Sentence.Abreviations.Adjective] = true
Sentence.ModifiersAbreviation[Sentence.Abreviations.DirectObject] = true
Sentence.ModifiersAbreviation[Sentence.Abreviations.DirectObjectComplement] = true
Sentence.ModifiersAbreviation[Sentence.Abreviations.IndirectObject] = true

---

function Sentence:initialize(descriptor)

	--[[ 
		descriptor format:

		descriptorExampleWithOneClause =
		{
			s = {"Character", "Toto"},
			p =
			{
				{ v = "isAt", doj = {"Location", "Home"}},
				{ c = "and", v = "isA", doj = {"FunnyGuy"} },
			}
		}

		in english, this sentence would translate like this:
		"Toto is at home and is a funny guy."

		descriptorExampleWithTwoClausesAndMatchingVariables =
		{
			{
				s = {"Prop", "GoldenKey"},
				p =
				{
					{ v = "isAt", doj = {"Location", "*goldenKeyLocation"}},
					{ c = "and", v = "isOn", doj = {"Ground"} },
				}
			},
			{
				s = {"Location", "@goldenKeyLocation"},
				p = {{ v = "isNear", doj = {"*", "@me"}}}
			}
		}

		in english, this sentence would translate like this:
		"Does the GoldenKey is somewhere on the ground and does its location is near me?"
	]]--
		
	self.isSentence = true
	self.clauses = {}
	self.matchingVariables = {}
	
	if 
		(not descriptor) or
		descriptor[Sentence.Abreviations.Subject]
	then
		
		-- initialize with only one clause in the descriptor
		
		self:addClause(descriptor)
	else

		-- initialize with only multiple clauses in the descriptor
		
		for clauseIndex, clauseDescriptor in ipairs(descriptor)
		do
			self:addClause(clauseDescriptor)
		end
	end
end

---

function Sentence:addClause(descriptor)

	local newClauseIndex = #(self.clauses) + 1
	local newClause = {}
	newClause.predicates = {}
	self.clauses[newClauseIndex] = newClause
	
	if not descriptor
	then
	
		-- initialize by default
		
		newClause.subjectEntityType = false
		newClause.subjectEntityName = false
		self:addPredicate(newClauseIndex)
	else
		
		-- initialize by descriptor
		
		local subjectDescriptor = descriptor[Sentence.Abreviations.Subject]
		newClause.subjectEntityType = subjectDescriptor[1]
		newClause.subjectEntityName = subjectDescriptor[2]
		
		for i, predicateDescriptor in ipairs(descriptor[Sentence.Abreviations.Predicate])
		do
			self:addPredicate(newClauseIndex, predicateDescriptor)
		end
	end
end

---

function Sentence:addPredicate(clauseIndex, parameter)

	local clause = self.clauses[clauseIndex]
	local newPredicateIndex = #(clause.predicates) + 1
	local newPredicate = {}
	clause.predicates[newPredicateIndex] = newPredicate
	newPredicate.modifiers = {}
	newPredicate.adverbs = {}
	newPredicate.negation = false
	
	if type(parameter) ~= "table"
	then
	
		-- initialize by default
		
		if not parameter
		then
			newPredicate.conjuction = ""
		else	
			newPredicate.conjuction = parameter
		end
		
		newPredicate.verb = false
	else
	
		-- initialize from descriptor
		
		local predicateDescriptor = parameter
		
		
		-- conjuction
		
		local conjuctionDescriptor = predicateDescriptor[Sentence.Abreviations.Conjuction]
			
		if not conjuctionDescriptor
		then
			newPredicate.conjuction = ""
		else
			newPredicate.conjuction = conjuctionDescriptor
		end


		-- adverbs
		
		local adverbsDescriptor = predicateDescriptor[Sentence.Abreviations.Adverb]
			
		if adverbsDescriptor
		then
			for i, adverb in ipairs(adverbsDescriptor)
			do
				if adverb == Sentence.Abreviations.Negation
				then 
					newPredicate.negation = true
				end
				newPredicate.adverbs[i] = adverb
			end
		end


		-- verb
		
		self:setVerb(predicateDescriptor[Sentence.Abreviations.Verb], clauseIndex, newPredicateIndex)
		
		
		-- modifiers
		
		for label, elementDescriptor in pairs(predicateDescriptor)
		do
			if Sentence.ModifiersAbreviation[label]
			then
				self:setModifier(clauseIndex, newPredicateIndex, label, elementDescriptor[1], tostring(elementDescriptor[2]))
			end
		end
	end
	
	return newPredicateIndex
end

---

function Sentence:setSubject(entity, clause)
	
	if not clause
	then
		clause = 1
	end
	
	self.clauses[clause].subjectEntityName = entity.name
	self.clauses[clause].subjectEntityType = entity.type
end

---

function Sentence:getSubject(clause)
	
	if not clause
	then
		clause = 1
	end
	
	return gField.entityTypes[self.clauses[clause].subjectEntityType][self.clauses[clause].subjectEntityName]
end

---

function Sentence:setVerb(verb, clause, predicate)
	
	if not clause
	then
		clause = 1
	end
	
	if not predicate
	then
		predicate = 1
	end
	
	self.clauses[clause].predicates[predicate].verb = verb
end

---

function Sentence:getVerb(clause, predicate)
	
	if not clause
	then
		clause = 1
	end
	
	if not predicate
	then
		predicate = 1
	end
	
	return self.clauses[clause].predicates[predicate].verb
end

---

function Sentence:setAdverb(adverb, clause, predicate)
	
	if not clause
	then
		clause = 1
	end
	
	if not predicate
	then
		predicate = 1
	end
	
	self.clauses[clause].predicates[predicate].adverbs[adverb] = true
end

---

function Sentence:getAdverbs(clause, predicate)
	
	if not clause
	then
		clause = 1
	end
	
	if not predicate
	then
		predicate = 1
	end
	
	return self.clauses[clause].predicates[predicate].adverbs
end

---

function Sentence:setDirectObject(parameter1, parameter2, clause, predicate)
	
	if not clause
	then
		clause = 1
	end
	
	if not predicate
	then
		predicate = 1
	end

	self:setModifier(clause, predicate, Sentence.Abreviations.DirectObject, parameter1, parameter2)
end

---

function Sentence:getDirectObject(clause, predicate)
	
	if not clause
	then
		clause = 1
	end
	
	if not predicate
	then
		predicate = 1
	end
	
	return self:getModifier(clause, predicate, Sentence.Abreviations.DirectObject)
end

---

function Sentence:getDirectObjectName(clause, predicate)
	
	if not clause
	then
		clause = 1
	end
	
	if not predicate
	then
		predicate = 1
	end
	
	return self.clauses[clause].predicates[predicate].modifiers[Sentence.Abreviations.DirectObject].name
end

---

function Sentence:setDirectObjectComplement(parameter1, parameter2, clause, predicate)
	
	if not clause
	then
		clause = 1
	end
	
	if not predicate
	then
		predicate = 1
	end

	self:setModifier(clause, predicate, Sentence.Abreviations.DirectObjectComplement, parameter1, parameter2)
end

---

function Sentence:getDirectObjectComplement(clause, predicate)
	
	if not clause
	then
		clause = 1
	end
	
	if not predicate
	then
		predicate = 1
	end
	
	return self:getModifier(clause, predicate, Sentence.Abreviations.DirectObjectComplement)
end

---

function Sentence:setIndirectObject(parameter1, parameter2, clause, predicate)
	
	if not clause
	then
		clause = 1
	end
	
	if not predicate
	then
		predicate = 1
	end

	self:setModifier(clause, predicate, Sentence.Abreviations.IndirectObject, parameter1, parameter2)
end

---

function Sentence:getIndirectObject(clause, predicate)
	
	if not clause
	then
		clause = 1
	end
	
	if not predicate
	then
		predicate = 1
	end
	
	return self:getModifier(clause, predicate, Sentence.Abreviations.IndirectObject)
end

---

function Sentence:setModifier(clause, predicate, modifier, parameter1, parameter2)

	if 
		(type(parameter1) == "table") and
		(not parameter1.isEntity) and
		parameter1.isSentence
	then
		self.clauses[clause].predicates[predicate].modifiers[modifier] = 
		{ 
			sentence	= parameter1, 
			string 		= tostring(parameter1)						--  string only used by the __tostring() function
		}	
	elseif (type(parameter1) == "string") 
	then
		self.clauses[clause].predicates[predicate].modifiers[modifier] = 
		{ 
			type 		= parameter1, 
			name 		= parameter2, 
			string 		= parameter1 .. ":" .. parameter2
		}	
	else	
		self.clauses[clause].predicates[predicate].modifiers[modifier] = 
		{ 
			type 		= parameter1.type, 
			name 		= parameter1.name, 
			string 		= parameter1.type .. ":" .. parameter1.name
		}
	end
end

---

function Sentence:getModifier(clause, predicate, modifier)

	local modifierDescriptor = self.clauses[clause].predicates[predicate].modifiers[modifier]
	
	if modifierDescriptor.sentence
	then
		return modifierDescriptor.sentence
	else
		return gField.entityTypes[modifierDescriptor.type][modifierDescriptor.name]
	end
end

---

function Sentence:equal(sentence, matchingVariablesRepository, flags)

	if not flags
	then
		flags = {}
	end

	if not matchingVariablesRepository
	then
		matchingVariablesRepository = {}
	end
	
	self.matchingVariables = matchingVariablesRepository
	sentence.matchingVariables = matchingVariablesRepository
	
	for clauseIndex, clause in ipairs(self.clauses)
	do
		local sentenceClause = sentence.clauses[clauseIndex]
		
		if not sentenceClause
		then
			return false
		end
		
		
		-- compare subject
		
		if 
			(not self:elementsEqual(clause.subjectEntityType, sentenceClause.subjectEntityType)) or
			(not self:elementsEqual(clause.subjectEntityName, sentenceClause.subjectEntityName))
		then
			return false
		end
	
		for predicateIndex, predicate in ipairs(clause.predicates)
		do	
			local sentencePredicate = sentenceClause.predicates[predicateIndex]
			
			local negation = predicate.negation
			if sentencePredicate.negation
			then
				negation = not negation
			end
			
			local equal = not negation
			
			if not sentencePredicate
			then
				equal = predicate.negation
			end
		
		
			-- compare conjuction and verb
				
			if 
				(
					(not flags.ignoreConjuctions) and
					(not self:elementsEqual(predicate.conjuction, sentencePredicate.conjuction))
				) or
				(not self:elementsEqual(predicate.verb, sentencePredicate.verb))
			then
				equal = negation
			end


			-- compare adverbs

			for adverbIndex, adverb in ipairs(predicate.adverbs)
			do
				if adverb ~= Sentence.Abreviations.Negation							-- we ignore the negation adverb that is handled differnetly 
				then
					if not self:elementsEqual(adverb, sentencePredicate.adverbs[adverbIndex])
					then
						equal = negation
						break
					end
				end
			end


			-- compare modifiers
			
			for modifierLabel, modifier in pairs(predicate.modifiers)
			do
				for modifierElementName, modifierElementValue in pairs(modifier)
				do
					if modifierElementName ~= "string"								-- we ignore the element name "string" because it's a 
					then
						if not self:elementsEqual(modifierElementValue, sentencePredicate.modifiers[modifierLabel][modifierElementName])
						then
							equal = negation
							break
						end
					end
				end
			end
			
			if not equal
			then
				return false
			end
		end		
	end
	
	return true
end

---

function Sentence:elementsEqual(element1, element2)

	local wildcard = false
	local element1IsATable = false
	local element2IsATable = false


	-- handle matching variables for element1

	if element1
	then
		if element1:sub(1, 1) == "*"
		then
			wildcard = true

			if element1:len() > 1
			then
				local machingVariableName = element1:sub(2, element1:len())
				self.matchingVariables[machingVariableName] = element2
			end
		elseif element1:sub(1, 1) == "@"
		then
			local machingVariableName
			
			if element1:sub(2, 2) == "{"
			then
				machingVariableName = element1:sub(3, element1:len() - 1)
				element1IsATable = true
			else
				machingVariableName = element1:sub(2, element1:len())
			end

			if self.matchingVariables[machingVariableName]
			then
				element1 = self.matchingVariables[machingVariableName]
			end
		end
	end


	-- handle matching variables for element2

	if element2
	then
		if element2:sub(1, 1) == "*"
		then
			wildcard = true

			if element2:len() > 1
			then
				local machingVariableName = element2:sub(2, element2:len())
				self.matchingVariables[machingVariableName] = element1
			end
		elseif element2:sub(1, 1) == "@"
		then
			local machingVariableName
			
			if element2:sub(2, 2) == "{"
			then
				machingVariableName = element2:sub(3, element2:len() - 1)
				element2IsATable = true
			else
				machingVariableName = element2:sub(2, element2:len())
			end

			if self.matchingVariables[machingVariableName]
			then
				element2 = self.matchingVariables[machingVariableName]
			end
		end
	end
	
	
	-- return the comparaison
	
	if 
		not element1IsATable and
		not element2IsATable 
	then
	
		-- neither elements are tables
		
		return
		(
			(element1 == element2) or
			wildcard
		)
		
	elseif element1IsATable
	then
	
		if element2IsATable
		then
		
			-- both elements are tables
			
			for element1Key, element1Value in pairs(element1)
			do
				for element2Key, element2Value in pairs(element2)
				do
					if element1Value == element2Value
					then
						return true
					end
				end
			end
			
			return false
		else
		
			-- only element 1 is a table
			
			for element1Key, element1Value in pairs(element1)
			do
				if element1Value == element2
				then
					return true
				end
			end
			
			return false
		end
	else
	
		-- only element 2 is a table
		
		for element2Key, element2Value in pairs(element2)
		do
			if element2Value == element1
			then
				return true
			end
		end
		
		return false
	end 
end

---

function Sentence:split(verbWildCardForbiden)

	if 
		(#self.clauses == 1) and
		(#self.clauses[1].predicates == 1)
	then
		if verbWildCardForbiden
		then
			local verb = self:getVerb()
			assert(
				(verb:sub(1,1) ~= "*") and
				(verb:sub(1,1) ~= "@"),
				"verbs can't have a wildcard during matching"
			)
		end
		return {self}
	end
	
	local splitedSentences = {}
	
	for clauseIndex, clause in ipairs(self.clauses)
	do
		for predicateIndex, predicate in ipairs(clause.predicates)
		do
			if verbWildCardForbiden
			then
				assert(
					(predicate.verb:sub(1,1) ~= "*") and
					(predicate.verb:sub(1,1) ~= "@"),
					"verbs can't have a wildcard during matching"
				)
			end
			
			local newSplitedSentence = Sentence:new()
			
			newSplitedSentence.clauses[1].subjectEntityType = clause.subjectEntityType
			newSplitedSentence.clauses[1].subjectEntityName = clause.subjectEntityName
			newSplitedSentence.clauses[1].predicates[1].conjuction = predicate.conjuction
			newSplitedSentence.clauses[1].predicates[1].verb = predicate.verb
			newSplitedSentence.clauses[1].predicates[1].negation = predicate.negation
			newSplitedSentence.clauses[1].predicates[1].adverbs = predicate.adverbs
			newSplitedSentence.clauses[1].predicates[1].modifiers = predicate.modifiers
			
			splitedSentences[#splitedSentences + 1] = newSplitedSentence
		end
	end	
	
	return splitedSentences
end

---

function Sentence:__tostring()

	local string = Sentence.Abreviations.Sentence .. "{" 
	
	for clauseIndex, clause in ipairs(self.clauses)
	do
		string = string .. " < " .. clause.subjectEntityType .. ":" .. clause.subjectEntityName
			
		for predicateIndex, predicate in ipairs(clause.predicates)
		do
			string = 
				string .. " " .. 
				predicate.conjuction .. "[ " .. 
				predicate.verb

			if next(predicate.adverbs)
			then
				string = string .. " " .. Sentence.Abreviations.Adverb .. "("
				
				for adverbIndex, adverbName in ipairs(predicate.adverbs)
				do
					string = string .. " " .. adverbName
				end 
				
				string = string .. " )"
			end

			for modifierName, modifierValue in pairs(predicate.modifiers)
			do
				string = string .. " " .. modifierName .. "( " .. modifierValue.string .. " )"
			end 
			
			string = string .. " ]"
		end
		
		string = string .. " >"
	end
	
	return string .. " }"
end